﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Configuration;
using System.Data.Common;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Data;
using EvidenceManagement.Model;

namespace EvidenceManagement.Data.Common
{
    public sealed class DataRepository : IDataRepository
    {

        #region Fields

        readonly Database database;
        readonly ParameterCache parameterCache = new ParameterCache();

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="Repository"/> class.
        /// </summary>
        /// <param name="connectionKey">The connection string setting key.</param>
        internal DataRepository(string connectionSettingKey)
        {
            database = CreateConnection(connectionSettingKey);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the Db connection instance.
        /// </summary>
        /// <value>The Db connection to the data source.</value>
        private Database Database
        {
            get { return database; }
        }

        /// <summary>
        /// Gets the discovered data parameters cache using the database command text as the key.
        /// </summary>
        /// <value>The discovered data parameters.</value>
        private ParameterCache DiscoveredParameterCache
        {
            get { return parameterCache; }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Creates a database connection.
        /// </summary>
        /// <param name="connectionSettingKey">The connection string setting key.</param>
        /// <returns>Database</returns>
        private Database CreateConnection(string connectionSettingKey)
        {
            if (string.IsNullOrEmpty(connectionSettingKey))
                throw new ArgumentException("Connection setting value is null or empty. Invalid argument.");

            return DatabaseFactory.CreateDatabase(connectionSettingKey);
        }

        #endregion

        #region IRepositoryAccessor Members


        /// <summary>
        /// Creates a database command.
        /// </summary>
        /// <param name="commandType">Type of the command specifying how the command string will be intepreted.</param>
        /// <param name="commandText">The command string to execute.</param>
        /// <returns></returns>
        public DbCommand CreateCommand(CommandType commandType, string commandText)
        {
            DbCommand command = null;

            switch (commandType)
            {
                case CommandType.StoredProcedure:
                    command = Database.GetStoredProcCommand(commandText);
                    break;
                default:
                    command = Database.GetSqlStringCommand(commandText);
                    break;
            }

            return command;
        }

        /// <summary>
        /// Creates a transaction against a data source.
        /// </summary>
        /// <returns>IDbTransaction</returns>
        public DbTransaction CreateTransaction()
        {
            DbConnection connection = Database.CreateConnection();
            if (connection.State == ConnectionState.Closed)
                connection.Open();

            return connection.BeginTransaction();
        }

        /// <summary>
        /// Creates a transaction against a data source
        /// </summary>
        /// <param name="isolationLevel">The isolation level.</param>
        /// <returns></returns>
        public DbTransaction CreateTransaction(IsolationLevel isolationLevel)
        {
            DbConnection connection = Database.CreateConnection();
            if (connection.State == ConnectionState.Closed)
                connection.Open();

            return connection.BeginTransaction(isolationLevel);
        }

        /// <summary>
        /// Retrieves parameter information from the stored procedure specified in the DbCommand and populates the Parameters collection of the specified DbCommand object.
        /// </summary>
        /// <param name="command">The DbCommand referencing the stored procedure from which the parameter information is to be derived.
        /// The derived parameters are added to the Parameters collection of the DbCommand.</param>
        /// <returns></returns>
        public void DiscoverDbCommandParameters(DbCommand command)
        {
            DiscoveredParameterCache.SetParameters(command, Database);
        }

        /// <summary>
        /// Adds the command parameter.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <param name="name">The name.</param>
        /// <param name="dataType">Type of the data.</param>
        /// <param name="value">The value.</param>
        public void AddCommandParameter(DbCommand command, string name, DbType dataType, object value)
        {
            AddCommandParameter(command, name, dataType, value, ParameterDirection.Input, 0);
        }

        /// <summary>
        /// Adds command parameter to the specified database command's parameter collection.
        /// </summary>
        /// <param name="command">The database command to add a data parameter to.</param>
        /// <param name="name">The data parameter name.</param>
        /// <param name="dataType">The data type of the data parameter.</param>
        /// <param name="value">The value to assign to the data parameter.</param>
        /// <param name="parameterDirection">The data parameter direction.</param>
        /// <returns></returns>
        public void AddCommandParameter(DbCommand command, string name, DbType dataType, object value, ParameterDirection parameterDirection)
        {
            AddCommandParameter(command, name, dataType, value, parameterDirection, 0);
        }

        /// <summary>
        /// Adds command parameter to the specified database command's parameter collection.
        /// </summary>
        /// <param name="command">The database command to add a data parameter to.</param>
        /// <param name="name">The data parameter name.</param>
        /// <param name="dataType">The data type of the data parameter.</param>
        /// <param name="value">The value to assign to the data parameter.</param>
        /// <param name="parameterDirection">The data parameter direction.</param>
        /// <param name="size">The size fo the data parameter.</param>
        /// <returns></returns>
        public void AddCommandParameter(DbCommand command, string name, DbType dataType, object value, ParameterDirection parameterDirection, int size)
        {
            switch (parameterDirection)
            {
                case ParameterDirection.Input:
                    Database.AddInParameter(command, name, dataType, value); ;
                    break;
                case ParameterDirection.InputOutput:
                    if (value != null)
                        Database.AddInParameter(command, name, dataType, value);
                    else
                        Database.AddOutParameter(command, name, dataType, size);
                    break;
                default:
                    Database.AddOutParameter(command, name, dataType, size);
                    break;
            }
        }

        /// <summary>
        /// Executes a database command and builds an IDataReader.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <returns>IDataReader resultset.</returns>
        public IDataReader ExecuteDataReader(DbCommand command)
        {
            return Database.ExecuteReader(command);
        }

        /// <summary>
        /// Executes a database command and builds an IDataReader.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <param name="transaction">The transaction to be performed against the datasource.</param>
        /// <returns>IDataReader resultset.</returns>
        public IDataReader ExecuteDataReader(DbCommand command, DbTransaction transaction)
        {
            return Database.ExecuteReader(command, transaction);
        }

        /// <summary>
        ///  Executes the database command, and returns the first column of the first row in the
        ///  resultset returned by the query. Extra columns or rows are ignored.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <returns>The first column of the first row in the resultset.</returns>
        public object ExecuteScalar(DbCommand command)
        {
            return Database.ExecuteScalar(command);
        }

        /// <summary>
        ///  Executes the database command in a transactional scope, and returns the first column of the first row in the
        ///  resultset returned by the query. Extra columns or rows are ignored.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <param name="transaction">The transaction to be performed against the datasource.</param>
        /// <returns>The first column of the first row in the resultset.</returns>
        public object ExecuteScalar(DbCommand command, DbTransaction transaction)
        {
            return Database.ExecuteScalar(command, transaction);
        }

        /// <summary>
        /// Executes the database command against the data source, and returns the number of rows affected.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <returns>Number of rows affected</returns>
        public int ExecuteNonQuery(DbCommand command)
        {
            return Database.ExecuteNonQuery(command);
        }

        /// <summary>
        /// Executes the database command in a transactional scope against the data source, and returns the number of rows affected.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <param name="transaction">The transaction to be performed against the datasource.</param>
        /// <returns>Number of rows affected</returns>
        public int ExecuteNonQuery(DbCommand command, DbTransaction transaction)
        {
            return Database.ExecuteNonQuery(command, transaction);
        }

        /// <summary>
        /// Executes a database command and returns a DataSet.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <returns>DataSet</returns>
        public DataSet ExecuteDataSet(DbCommand command)
        {
            return Database.ExecuteDataSet(command);
        }

        /// <summary>
        /// Executes a database command and returns a DataSet.
        /// </summary>
        /// <param name="command">The database command to execute.</param>
        /// <param name="transaction">The transaction to be performed against the datasource.</param>
        /// <returns>DataSet</returns>
        public DataSet ExecuteDataSet(DbCommand command, DbTransaction transaction)
        {
            return Database.ExecuteDataSet(command, transaction);
        }

        #endregion
        
    }
}
